package com.asggo.g21.utils.compare;

import com.asggo.g21.ex.G21Exception;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import org.springframework.beans.BeanUtils;
import org.springframework.lang.Nullable;

/**
 * Created by IntelliJ IDEA.
 *
 * @author eric 2022/12/2 10:09
 */
public class CompareUtils<T> {

  private static final String COMMA = "\\n";
  private static final String EMPTY = "";
  private static final String INIT_TEMPLATE = "[%s: %s]";
  private static final String COMPARE_TEMPLATE = "[%s: %s -> %s]";

  /**
   * 属性比较
   *
   * @param source           源数据对象.
   * @param target           目标数据对象.
   * @param ignoreProperties 忽略比较的字段.
   * @return 对应属性值的比较变化
   */
  public List<DiffNode> compareNode(T source, T target, @Nullable String... ignoreProperties) {
    return compareNode(source, target, Map.of(), ignoreProperties);
  }


  /**
   * 属性比较
   *
   * @param source           源数据对象.
   * @param target           目标数据对象.
   * @param metaMap          元数据映射.
   * @param ignoreProperties 忽略比较的字段.
   * @return 对应属性值的比较变化
   */
  public List<DiffNode> compareNode(T source, T target, Map<Object, Object> metaMap,
      @Nullable String... ignoreProperties) {
    if (Objects.isNull(source) && Objects.isNull(target)) {
      return List.of();
    }
    if (Objects.isNull(target) || Objects.isNull(metaMap)) {
      return List.of();
    }
    Map<String, CompareNode> sourceMap = this.getFiledValueMap(source, metaMap);
    Map<String, CompareNode> targetMap = this.getFiledValueMap(target, metaMap);
    if (sourceMap.isEmpty() && targetMap.isEmpty()) {
      return List.of();
    }
    // 如果源数据为空，则只显示目标数据，不显示属性变化情况
    if (sourceMap.isEmpty()) {
      return doInitNode(targetMap, ignoreProperties);
    }
    // 如果源数据不为空，则显示属性变化情况
    return doCompareNode(sourceMap, targetMap, ignoreProperties);
  }

  private List<DiffNode> doInitNode(Map<String, CompareNode> targetMap,
      @Nullable String... ignoreProperties) {
    Collection<CompareNode> values = targetMap.values();
    List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
    List<DiffNode> diffNodes = new LinkedList<>();
    for (CompareNode node : values) {
      Object o = Optional.ofNullable(node.getFieldValue()).orElse(EMPTY);
      if (Objects.nonNull(ignoreList) && ignoreList.contains(node.getFieldKey())) {
        continue;
      }

      if (!o.toString().isEmpty()) {
        DiffNode diffNode = new DiffNode();
        diffNodes.add(diffNode);

        diffNode.setFieldName(node.getFieldName());
        diffNode.setFieldKey(node.getFieldKey());
        diffNode.setSourceFieldValue(null);
        diffNode.setTargetFieldValue(o);
      }
    }
    return diffNodes;
  }

  private List<DiffNode> doCompareNode(Map<String, CompareNode> sourceMap,
      Map<String, CompareNode> targetMap,
      @Nullable String... ignoreProperties) {
    Set<String> keys = sourceMap.keySet();
    List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
    List<DiffNode> diffNodes = new LinkedList<>();
    for (String key : keys) {
      CompareNode sn = sourceMap.get(key);
      CompareNode tn = targetMap.get(key);
      if (Objects.nonNull(ignoreList) && ignoreList.contains(sn.getFieldKey())) {
        continue;
      }
      String sv = Optional.ofNullable(sn.getFieldValue()).orElse(EMPTY).toString();
      String tv = Optional.ofNullable(tn.getFieldValue()).orElse(EMPTY).toString();
      // 只有两者属性值不一致时, 才显示变化情况
      if (!sv.equals(tv)) {
        DiffNode diffNode = new DiffNode();
        diffNode.setFieldKey(sn.getFieldKey());
        diffNode.setFieldName(sn.getFieldName());
        diffNode.setSourceFieldValue(sv);
        diffNode.setTargetFieldValue(tv);

        diffNodes.add(diffNode);
      }
    }
    return diffNodes;
  }

  /**
   * 属性比较
   *
   * @param source           源数据对象.
   * @param target           目标数据对象.
   * @param ignoreProperties 忽略比较的字段.
   * @return 对应属性值的比较变化
   */
  public String compare(T source, T target, @Nullable String... ignoreProperties) {
    return compare(source, target, Map.of(), ignoreProperties);
  }


  /**
   * 属性比较
   *
   * @param source           源数据对象.
   * @param target           目标数据对象.
   * @param metaMap          元数据映射.
   * @param ignoreProperties 忽略比较的字段.
   * @return 对应属性值的比较变化
   */
  public String compare(T source, T target, Map<Object, Object> metaMap,
      @Nullable String... ignoreProperties) {
    final List<DiffNode> diffNodes = compareNode(source, target, metaMap, ignoreProperties);
    if (diffNodes.isEmpty()) {
      return EMPTY;
    }

    return diffNodes
        .stream()
        .map(node -> {
              if (Objects.isNull(source)) {
                return String.format(INIT_TEMPLATE, node.getFieldKey(), node.getTargetFieldValue());
              }
              return String.format(COMPARE_TEMPLATE, node.getFieldKey(),
                  node.getSourceFieldValue(), node.getTargetFieldValue());
            }
        )
        .reduce(EMPTY, (a, b) -> a + COMMA
            + b);
  }

  private Map<String, CompareNode> getFiledValueMap(T t, Map<Object, Object> metaMap) {
    if (Objects.isNull(t)) {
      return Collections.emptyMap();
    }
    Field[] fields = t.getClass().getDeclaredFields();
    if (fields.length == 0) {
      return Collections.emptyMap();
    }
    Map<String, CompareNode> map = new LinkedHashMap<>();
    for (Field field : fields) {
      Compare compareAnnotation = field.getAnnotation(Compare.class);
      PropertyDescriptor descriptor =
          BeanUtils.getPropertyDescriptor(t.getClass(), field.getName());
      if (Objects.isNull(compareAnnotation) || Objects.isNull(descriptor)) {
        continue;
      }

      try {
        String fieldKey = field.getName();
        CompareNode node = new CompareNode();
        node.setFieldKey(fieldKey);
        final Method readMethod = descriptor.getReadMethod();
        final Object o = readMethod.invoke(t);
        node.setFieldValue(compareAnnotation.isMeta() ? metaMap.getOrDefault(o, o)
            : o);
        node.setFieldName(compareAnnotation.value());
        map.put(field.getName(), node);
      } catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
        throw new G21Exception(e);
      }
    }
    return map;
  }

  public static void main(String[] args) {
    User u1 = new User();
    u1.setSex(1);
    u1.setName("张三");
    u1.setAge(13);
    u1.setAddress("北京");

    User u2 = new User();
    u2.setSex(2);
    u2.setName("李四");
    u2.setAge(13);
    u2.setAddress("上海");

    String diffText = new CompareUtils<User>().compare(u1, u2);
    System.out.println(diffText);
    diffText = new CompareUtils<User>().compare(u1, u2, Map.of(1, "男", 2, "女", 0, "未知"));
    System.out.println(diffText);
    diffText = new CompareUtils<User>().compare(null, u1);
    System.out.println(diffText);
  }
}
